﻿#include "pch.h"
#include "sbcs.h"
#include "sbcsp.h"
#if !_SB_WINDOWS_
#include <termios.h>
#include <fcntl.h>
#include <sys/poll.h>   // 애플 기종에는 안됨
#include <sys/ioctl.h>
#endif

#if _SB_WINDOWS_STORE_ || _SB_WINDOWS_PHONE_
#define NO_UART_SUPP		1
#endif

//////////////////////////////////////////////////////////////////////////
// 시리얼 포트
struct kSerialCom
{
	kintptr				handle;

	kcham				isconn;
	kint				flags;

#if _SB_WINDOWS_
	kcham				ispending;
	HANDLE				ov_event;

	HANDLE				ov_recv;
	HANDLE				ov_send;
#else
	kcham				useevent;
#endif

	struct prop
	{
		char				port[64];

		int					baudrate;
		int					databit : 8;
		int					stopbit : 8;
		int					parity : 8;
		int					handshake : 8;

		int					size_send;
		int					size_recv;

		int					to_send;
		int					to_recv;

		kcham				enable_dtr;
		kcham				enable_rts;
		kcham				enable_break;
	}					prop;
};

/**
 * 시리얼 포트 만들기.
 * @param	flags	플래그 KSERF_ 에 해당하는 값을 넣음.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 kSerialCom*.
 */
kSerialCom* k_sric_new(int flags)
{
#if NO_UART_SUPP
	return NULL;
#else
	kSerialCom* self = k_new_0(kSerialCom);

#if _SB_WINDOWS_
	k_strcpy(self->prop.port, "COM1");
#elif _SB_ANDROID_
	k_strcpy(self->prop.port, "/dev/s3c2410_serial");
#elif _SB_BSD_
	k_strcpy(self->prop.port, "/dev/cuaa0");
#elif _SB_QNX_
	k_strcpy(self->prop.port, "/dev/ser0");		// usb : serusb
#elif _SB_SCO_
	k_strcpy(self->prop.port, "/dev/tty1a");
#elif _SB_LINUX_
	k_strcpy(self->prop.port, "/dev/ttyS0");	// usb : ttyUSB
#endif

	self->flags = flags;

	self->prop.baudrate = 9600;
	self->prop.databit = 8;
	self->prop.stopbit = KSERSTOP_1;
	self->prop.parity = KSERPAR_NONE;
	self->prop.handshake = KSERHS_NONE;

	self->prop.size_send = 2048;
	self->prop.size_recv = 2048;

	self->prop.to_send = -1;
	self->prop.to_recv = -1;

	self->prop.enable_dtr = FALSE;
	self->prop.enable_rts = FALSE;
	self->prop.enable_break = FALSE;

	return self;
#endif
}

/**
 * 시리얼 포트를 제거.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 */
void k_sric_delete(kSerialCom* self)
{
#if !NO_UART_SUPP	
	k_sric_close(self);
	k_delete(self);
#endif
}

// 속성
#if !NO_UART_SUPP
static kcham _k_sric_set_attr(kintptr handle, int baudrate, int parity, int databit, int stopbit, int handshake)
{
#if _SB_WINDOWS_
	HANDLE h = (HANDLE)handle;
	DCB dcb;

	// 속성
	memset(&dcb, 0, sizeof(DCB));
	dcb.DCBlength = sizeof(DCB);

	if (!GetCommState(h, &dcb))
		return FALSE;

	dcb.BaudRate = (DWORD)baudrate;
	dcb.ByteSize = (BYTE)databit;
	dcb.Parity = (BYTE)parity;

	switch (stopbit)
	{
		case KSERSTOP_1:
			dcb.StopBits = 0;
			break;

		case KSERSTOP_1_5:
			dcb.StopBits = 1;
			break;

		case KSERSTOP_2:
			dcb.StopBits = 2;
			break;
	}

	dcb.fOutxCtsFlow = 0;
	dcb.fOutX = 0;
	dcb.fInX = 0;
	dcb.fRtsControl = 0;

	switch (handshake)
	{
		case KSERHS_XONXOFF:
			dcb.fOutX = 1;
			dcb.fInX = 1;
			break;

		case KSERHS_REQTOSEND:
			dcb.fOutxCtsFlow = 1;
			dcb.fRtsControl = 1;
			break;

		case KSERHS_REQTOSENDXONXOFF:
			dcb.fOutX = 1;
			dcb.fInX = 1;
			dcb.fOutxCtsFlow = 1;
			dcb.fRtsControl = 1;
			break;
	}

	if (!SetCommState(h, &dcb))
		return FALSE;

	return TRUE;
#else
	int fd = (int)handle;
	struct termios nts;

	if (tcgetattr(fd, &nts) < 0)
		return FALSE;

	nts.c_cflag |= (CLOCAL | CREAD);
	nts.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG | IEXTEN);
	nts.c_oflag &= ~(OPOST);
	nts.c_iflag = IGNBRK;

	// 보레이트
	switch (baudrate)
	{
#ifdef B921600
		case 921600:
			baudrate = B921600;
			break;
#endif

#ifdef B460800
		case 460800:
			baudrate = B460800;
			break;
#endif

		case 230400:
			baudrate = B230400;
			break;

		case 115200:
			baudrate = B115200;
			break;

		case 57600:
			baudrate = B57600;
			break;

		case 38400:
			baudrate = B38400;
			break;

		case 19200:
			baudrate = B19200;
			break;

		case 9600:
			baudrate = B9600;
			break;

		case 4800:
			baudrate = B4800;
			break;

		case 2400:
			baudrate = B2400;
			break;

		case 1800:
			baudrate = B1800;
			break;

		case 1200:
			baudrate = B1200;
			break;

		case 600:
			baudrate = B600;
			break;

		case 300:
			baudrate = B300;
			break;

		case 200:
			baudrate = B200;
			break;

		case 150:
			baudrate = B150;
			break;

		case 134:
			baudrate = B134;
			break;

		case 110:
			baudrate = B110;
			break;

		case 75:
			baudrate = B75;
			break;

		default:
			// 기본값은 9600보
			baudrate = B9600;
			break;
	}

	// 데이터 비트
	nts.c_cflag &= ~CSIZE;

	switch (databit)
	{
		case 5:
			nts.c_cflag |= CS5;
			break;

		case 6:
			nts.c_cflag |= CS6;
			break;

		case 7:
			nts.c_cflag |= CS7;
			break;

		default:
			// 기본갑 8비트
			nts.c_cflag |= CS8;
			break;
	}

	// 스탑 비트
	switch (stopbit)
	{
		case KSERSTOP_1:
			nts.c_cflag &= ~CSTOPB;
			break;

		case KSERSTOP_1_5:
			// 이런거 없음
			break;

		case KSERSTOP_2:
			nts.c_cflag |= CSTOPB;
			break;
	}

	// 패리티
	nts.c_iflag &= ~(INPCK | ISTRIP);

	switch (parity)
	{
		case KSERPAR_NONE:
			nts.c_cflag &= ~(PARENB | PARODD);
			break;

		case KSERPAR_ODD:
			nts.c_cflag |= (PARENB | PARODD);
			break;

		case KSERPAR_EVEN:
			nts.c_cflag &= ~(PARODD);
			nts.c_cflag |= PARENB;
			break;

		case KSERPAR_MARK:
		case KSERPAR_SPACE:
			// 이런거 없음
			break;
	}

	// 핸드세이크
	nts.c_iflag &= ~(IXOFF | IXON);
#ifdef CRTSCTS
	nts.c_cflag &= ~(CRTSCTS);
#endif

	switch (handshake)
	{
		case KSERHS_NONE:
			break;

		case KSERHS_REQTOSEND:
#ifdef CRTSCTS
			nts.c_cflag |= CRTSCTS;
#endif
			break;

		case KSERHS_REQTOSENDXONXOFF:
#ifdef CRTSCTS
			nts.c_cflag |= CRTSCTS;
#endif
			nts.c_iflag |= IXOFF | IXON;
			break;

		case KSERHS_XONXOFF:
			nts.c_iflag |= IXOFF | IXON;
			break;
	}

	// 속도 및 설정
	if (cfsetospeed(&nts, baudrate) < 0 ||
		cfsetispeed(&nts, baudrate) < 0 ||
		tcsetattr(fd, TCSANOW, &nts) < 0)
		return FALSE;

	return TRUE;
#endif
}
#endif

/**
 * 시리얼 포트의 이름을 등록한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	name		시리얼 포트 이름.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_port(kSerialCom* self, const char* name)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(!self->isconn, FALSE);
	k_return_value_if_fail(name != NULL, FALSE);

	k_strncpy(self->prop.port, name, 64 - 1);

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 보레이트를 지정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	baudrate	보레이트.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_baud(kSerialCom* self, int baudrate)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(baudrate > 0, FALSE);

	if (self->isconn && self->prop.baudrate != baudrate)
	{
		if (!_k_sric_set_attr(self->handle, baudrate, self->prop.parity, self->prop.databit, self->prop.stopbit, self->prop.handshake))
			return FALSE;
	}

	self->prop.baudrate = baudrate;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 데이터 비트를 지정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	databit 	데이터 비트.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_databit(kSerialCom* self, int databit)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(databit >= 5 && databit <= 8, FALSE);

	if (self->isconn && self->prop.databit != databit)
	{
		if (!_k_sric_set_attr(self->handle, self->prop.baudrate, self->prop.parity, databit, self->prop.stopbit, self->prop.handshake))
			return FALSE;
	}

	self->prop.databit = databit;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 정지 비트를 지정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	stopbit 	정지 비트.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_stopbit(kSerialCom* self, int stopbit)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(stopbit >= KSERSTOP_1 && stopbit <= KSERSTOP_2, FALSE);

	if (self->isconn && self->prop.stopbit != stopbit)
	{
		if (!_k_sric_set_attr(self->handle, self->prop.baudrate, self->prop.parity, self->prop.databit, stopbit, self->prop.handshake))
			return FALSE;
	}

	self->prop.stopbit = stopbit;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 패리티를 지정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	parity  	패리티.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_parity(kSerialCom* self, int parity)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(parity >= KSERPAR_NONE && parity <= KSERPAR_SPACE, FALSE);

	if (self->isconn && self->prop.parity != parity)
	{
		if (!_k_sric_set_attr(self->handle, self->prop.baudrate, parity, self->prop.databit, self->prop.stopbit, self->prop.handshake))
			return FALSE;
	}

	self->prop.parity = parity;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 핸드세이크를 지정한다.
 * @param [입력]	self 	개체나 인터페이스의 자기 자신 값.
 * @param	handshake	핸드세이크.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_handshake(kSerialCom* self, int handshake)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(handshake >= KSERHS_NONE && handshake <= KSERHS_REQTOSENDXONXOFF, FALSE);

	if (self->isconn && self->prop.handshake != handshake)
	{
		if (!_k_sric_set_attr(self->handle, self->prop.baudrate, self->prop.parity, self->prop.databit, self->prop.stopbit, handshake))
			return FALSE;
	}

	self->prop.handshake = handshake;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 브레이크 상태를 지정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	isbreak 	브레이크 상태 기능을 쓰려면 참으로 넣는다.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_break_state(kSerialCom* self, kcham isbreak)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(self->isconn, FALSE);

	if (self->flags & KSERF_NOBREAK)
		return FALSE;

#if _SB_WINDOWS_
	if (isbreak != self->prop.enable_break)
	{
		if (!EscapeCommFunction((HANDLE)self->handle, isbreak ? SETBREAK : CLRBREAK))
			return FALSE;
	}
#else
	if (isbreak)
	{
		if (tcsendbreak((int)self->handle, 0) < 0)
			return FALSE;
	}
#endif

	self->prop.enable_break = isbreak;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 DTR을 지정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	value   	기능을 쓰려면 참으로 넣는다.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_dtr_enable(kSerialCom* self, kcham value)
{
#if NO_UART_SUPP
	return FALSE;
#else
	if (self->flags & KSERF_NODTR)
		return FALSE;

#if _SB_WINDOWS_
	if (self->isconn && self->prop.enable_dtr != value)
	{
		DWORD dw = value ? SETDTR : CLRDTR;

		if (!EscapeCommFunction((HANDLE)self->handle, dw))
			return FALSE;
	}
#else
	if (self->isconn)
	{
		int fd = (int)self->handle;
		int sigs, tmp;

		if (ioctl(fd, TIOCMGET, &sigs) < 0)
			return FALSE;

		if (((sigs & TIOCM_DTR) != 0) != (value != 0))
		{
			tmp = value ? sigs | TIOCM_DTR : sigs&~(TIOCM_DTR);

			if (ioctl(fd, TIOCMSET, &tmp) < 0)
				return FALSE;
		}
	}
#endif

	self->prop.enable_dtr = value;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 RTS를 설정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	value   	기능을 쓰려면 참으로 넣는다.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_rts_enable(kSerialCom* self, kcham value)
{
#if NO_UART_SUPP
	return FALSE;
#else
	if (self->flags & KSERF_NORTS)
		return FALSE;

#if _SB_WINDOWS_
	if (self->isconn && self->prop.enable_dtr != value)
	{
		DWORD dw = value ? SETRTS : CLRRTS;

		if (!EscapeCommFunction((HANDLE)self->handle, dw))
			return FALSE;
	}
#else
	if (self->isconn)
	{
		int fd = (int)self->handle;
		int sigs, tmp;

		if (ioctl(fd, TIOCMGET, &sigs) < 0)
			return FALSE;

		if (((sigs & TIOCM_RTS) != 0) != (value != 0))
		{
			tmp = value ? sigs | TIOCM_RTS : sigs&~(TIOCM_RTS);

			if (ioctl(fd, TIOCMSET, &tmp) < 0)
				return FALSE;
		}
	}
#endif

	self->prop.enable_dtr = value;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 읽기 타임 아웃을 설정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	value   	타임아웃(밀리초 단위).
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_recv_timeout(kSerialCom* self, int value)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(value >= -1, FALSE);

#if _SB_WINDOWS_
	if (self->isconn && self->prop.to_recv != value)
	{
		COMMTIMEOUTS cto;
		cto.ReadIntervalTimeout = INFINITE;
		cto.ReadTotalTimeoutMultiplier = INFINITE;
		cto.ReadTotalTimeoutConstant = value == -1 ? INFINITE - 1 : (DWORD)value;
		cto.WriteTotalTimeoutMultiplier = 0;
		cto.WriteTotalTimeoutConstant = self->prop.to_send == -1 ? INFINITE - 1 : (DWORD)self->prop.to_send;

		if (!SetCommTimeouts((HANDLE)self->handle, &cto))
			return FALSE;
	}
#endif

	self->prop.to_recv = value;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 쓰기 타임 아웃을 설정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	value   	타임아웃(밀리초 단위).
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_send_timeout(kSerialCom* self, int value)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(value >= -1, FALSE);

#if _SB_WINDOWS_
	if (self->isconn && self->prop.to_send != value)
	{
		COMMTIMEOUTS cto;
		cto.ReadIntervalTimeout = INFINITE;
		cto.ReadTotalTimeoutMultiplier = INFINITE;
		cto.ReadTotalTimeoutConstant = self->prop.to_recv == -1 ? INFINITE - 1 : (DWORD)self->prop.to_recv;
		cto.WriteTotalTimeoutMultiplier = 0;
		cto.WriteTotalTimeoutConstant = value == -1 ? INFINITE - 1 : (DWORD)value;

		if (!SetCommTimeouts((HANDLE)self->handle, &cto))
			return FALSE;
	}
#endif

	self->prop.to_send = value;

	return TRUE;
#endif
}

/**
 * 시리얼 포트 읽기 버퍼 크기를 지정한다
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	value   	지정할 크기.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_recv_buf_size(kSerialCom* self, int value)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(!self->isconn, FALSE);
	k_return_value_if_fail(value > 0, FALSE);

	self->prop.size_recv = value;

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 쓰기 버퍼 크기를 지정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	value   	지정할 크기.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_send_buf_size(kSerialCom* self, int value)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(!self->isconn, FALSE);
	k_return_value_if_fail(value > 0, FALSE);

	self->prop.size_send = value;

	return TRUE;
#endif
}

/**
 * 시리얼 포트가 열려 있는지 확인한다
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	열려 있으면 참, 아니면 거짓.
 */
kcham k_sric_is_open(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
	return self->isconn;
#endif
}

/**
 * 시리얼 포트의 핸들을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	핸들 포인터.
 */
kintptr k_sric_get_handle(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->handle;
#endif
}

/**
 * 시리얼 포트의 포트 이름을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	포트 이름.
 */
const char* k_sric_get_port(kSerialCom* self)
{
#if NO_UART_SUPP
	return NULL;
#else
	return self->prop.port;
#endif
}

/**
 * 시리얼 포트의 보레이트 설정 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	보레이트.
 */
int k_sric_get_baud(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->prop.baudrate;
#endif
}

/**
 * 시리얼 포트의 데이터 비트 설정 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	데이터 비트.
 */
int k_sric_get_databit(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->prop.databit;
#endif
}

/**
 * 시리얼 포트의 정지 비트 설정 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	정지 비트.
 */
int k_sric_get_stopbit(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->prop.stopbit;
#endif
}

/**
 * 시리얼 포트의 패리티 설정 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	패리티.
 */
int k_sric_get_parity(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->prop.parity;
#endif
}

/**
 * 시리얼 포트의 핸드 세이크 설정 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	핸드 세이크.
 */
int k_sric_get_handshake(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->prop.handshake;
#endif
}

/**
 * 시리얼 포트의 브레이크 상태 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	브레이크 상태.
 */
kcham k_sric_get_break_state(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
	return self->prop.enable_break;
#endif
}

/**
 * 시리얼 포트의 DTR 상태 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	DTR 상태.
 */
kcham k_sric_get_dtr_enable(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
	return self->prop.enable_dtr;
#endif
}

/**
 * 시리얼 포트의 RTS 상태 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	RTS 상태.
 */
kcham k_sric_get_rts_enable(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
	return self->prop.enable_rts;
#endif
}

/**
 * 시리얼 포트의 읽기 타임 아웃 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	타임아웃(밀리초).
 */
int k_sric_get_recv_timeout(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->prop.to_recv;
#endif
}

/**
 * 시리얼 포트의 쓰기 타임 아웃 값을 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	타임아웃(밀리초).
 */
int k_sric_get_send_timeout(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->prop.to_send;
#endif
}

/**
 * 시리얼 포트의 읽기 버퍼 크기를 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	읽기 버퍼 크기.
 */
int k_sric_get_recv_buf_size(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->prop.size_recv;
#endif
}

/**
 * 시리얼 포트의 쓰기 버퍼 크기를 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	쓰기 버퍼 크기.
 */
int k_sric_get_send_buf_size(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	return self->prop.size_send;
#endif
}

/**
 * 시리얼 포트의 현재 버퍼 내의 읽을 수 있는 데이터의 크기를 얻는다.
 * 이 함수로 먼저 메모리를 할당한 다음 recv로 해당 크기 만큼 읽을 수 있으며,
 * 시리얼 포트 버퍼가 읽을 수 있는지 여부도 검사할 수 있다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	읽을 수 있는 데이터의 크기.
 */
int k_sric_get_size_to_recv(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
#if _SB_WINDOWS_
	COMSTAT cs;
	DWORD dw;

	k_return_value_if_fail(self->isconn, -1);

	if (!ClearCommError((HANDLE)self->handle, &dw, &cs))
		return -1;

	return cs.cbInQue;
#else
	int tmp;

	k_return_value_if_fail(self->isconn, -1);

	if (ioctl((int)self->handle, FIONREAD, &tmp) < 0)
		return -1;

	return tmp;
#endif
#endif
}

/**
 * 시리얼 포트의 보내야할 데이터의 크기.
 * 쓰기 버퍼에 들어 있는 데이터의 길이를 의미한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	보내야할 크기.
 */
int k_sric_get_size_to_send(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
#if _SB_WINDOWS_
	COMSTAT cs;
	DWORD dw;

	k_return_value_if_fail(self->isconn, -1);

	if (!ClearCommError((HANDLE)self->handle, &dw, &cs))
		return -1;

	return cs.cbOutQue;
#else
	int tmp;

	k_return_value_if_fail(self->isconn, -1);

	if (ioctl((int)self->handle, TIOCOUTQ, &tmp) < 0)
		return -1;

	return tmp;
#endif
#endif
}

/**
 * 시리얼 포트의 cd 홀딩 상태를 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	CD 홀딩 상태.
 */
kcham k_sric_get_cd_holding(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
#if _SB_WINDOWS_
	DWORD dw;

	k_return_value_if_fail(self->isconn, FALSE);

	if (!GetCommModemStatus((HANDLE)self->handle, &dw))
		return FALSE;

	return (dw & MS_RLSD_ON) != 0;
#else
	int sigs;

	k_return_value_if_fail(self->isconn, FALSE);

	if (ioctl((int)self->handle, TIOCMGET, &sigs) < 0)
		return FALSE;

	return (sigs & TIOCM_CAR) != 0;
#endif
#endif
}

/**
 * 시리얼 포트의 cts 홀딩 상태를 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	CTS 홀딩 상태.
 */
kcham k_sric_get_cts_holding(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
#if _SB_WINDOWS_
	DWORD dw;

	k_return_value_if_fail(self->isconn, FALSE);

	if (!GetCommModemStatus((HANDLE)self->handle, &dw))
		return FALSE;

	return (dw & MS_CTS_ON) != 0;
#else
	int sigs;

	k_return_value_if_fail(self->isconn, FALSE);

	if (ioctl((int)self->handle, TIOCMGET, &sigs) < 0)
		return FALSE;

	return (sigs & TIOCM_CTS) != 0;
#endif
#endif
}

/**
 * 시리얼 포트의 dsr 홀딩 상태를 얻는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	DSR 홀딩 상태.
 */
kcham k_sric_get_dsr_holding(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
#if _SB_WINDOWS_
	DWORD dw;

	k_return_value_if_fail(self->isconn, FALSE);

	if (!GetCommModemStatus((HANDLE)self->handle, &dw))
		return FALSE;

	return (dw & MS_DSR_ON) != 0;
#else
	int sigs;

	k_return_value_if_fail(self->isconn, FALSE);

	if (ioctl((int)self->handle, TIOCMGET, &sigs) < 0)
		return FALSE;

	return (sigs & TIOCM_DSR) != 0;
#endif
#endif
}

/**
 * 시리얼 포트를 연다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_open(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
#if _SB_WINDOWS_
	HANDLE h;
	wchar_t portname[K_MAX_PATH];
	COMMTIMEOUTS cto;
	DWORD dw;
#if !_SB_WINDOWS_DESKTOP_
	CREATEFILE2_EXTENDED_PARAMETERS cep;
#endif

	if (self->isconn)
		return FALSE;

	// 포트 열기
	k_getuni(portname, K_MAX_PATH - 1, self->prop.port);

#if _SB_WINDOWS_DESKTOP_
	h = CreateFile(portname, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
#else
	k_zero_1(&cep);
	cep.dwFileFlags = FILE_FLAG_OVERLAPPED;
	h = CreateFile2(portname, GENERIC_READ | GENERIC_WRITE, 0, OPEN_EXISTING, &cep);
#endif

	if (h == NULL || h == INVALID_HANDLE_VALUE)
		return FALSE;

	// 속성 설정
	if (!_k_sric_set_attr((kintptr)h, self->prop.baudrate, self->prop.parity, self->prop.databit, self->prop.stopbit, self->prop.handshake))
	{
		// 설정 실패
		goto pos_error;
	}

	// 버퍼 지우기
	if (!PurgeComm(h, PURGE_RXCLEAR | PURGE_TXCLEAR))
	{
		// 퍼지 실패
		goto pos_error;
	}

	// 버퍼 크기 설정
	if (!SetupComm(h, self->prop.size_recv, self->prop.size_send))
	{
		// 크기 설정 실패
		goto pos_error;
	}

	// 타임 아웃
	cto.ReadIntervalTimeout = INFINITE;
	cto.ReadTotalTimeoutMultiplier = INFINITE;
	cto.ReadTotalTimeoutConstant = self->prop.to_recv == -1 ? INFINITE - 1 : (DWORD)self->prop.to_recv;
	cto.WriteTotalTimeoutMultiplier = 0;
	cto.WriteTotalTimeoutConstant = self->prop.to_send == -1 ? INFINITE - 1 : (DWORD)self->prop.to_send;

	if (!SetCommTimeouts(h, &cto))
	{
		// 시간 설정 실패
		goto pos_error;
	}

	if ((self->flags & KSERF_NODTR) == 0)
	{
		// DTR
		dw = self->prop.enable_dtr ? SETDTR : CLRDTR;

		if (!EscapeCommFunction(h, dw))
		{
			// 설정 실패
			goto pos_error;
		}
	}

	// RTS
	if ((self->flags & KSERF_NORTS) == 0)
	{
		if (self->prop.handshake != KSERHS_REQTOSEND &&
			self->prop.handshake != KSERHS_REQTOSENDXONXOFF)
		{
			dw = self->prop.enable_rts ? SETRTS : CLRRTS;

			if (!EscapeCommFunction(h, dw))
			{
				// 설정 실패
				goto pos_error;
			}
		}
	}

	self->handle = (kintptr)h;

	self->ov_send = CreateEvent(NULL, TRUE, FALSE, NULL);
	self->ov_recv = CreateEvent(NULL, TRUE, FALSE, NULL);

	self->isconn = TRUE;

	return TRUE;

pos_error:
	CloseHandle(h);

	return FALSE;
#else
	int fd, sigs, tmp;

	if (self->isconn)
		return FALSE;

	// 열고
	fd = open(self->prop.port, O_RDWR | O_NOCTTY | O_NONBLOCK);

	if (fd < 0)
		return FALSE;

	// 속성
	if (!_k_sric_set_attr((kintptr)fd, self->prop.baudrate, self->prop.parity, self->prop.databit, self->prop.stopbit, self->prop.handshake))
		goto pos_error;

	// dtr
	if ((self->flags & KSERF_NODTR) == 0)
	{
		if (ioctl(fd, TIOCMGET, &sigs) < 0)
			goto pos_error;

		if (((sigs & TIOCM_DTR) != 0) != (self->prop.enable_dtr != 0))
		{
			tmp = self->prop.enable_dtr ? sigs | TIOCM_DTR : sigs&~(TIOCM_DTR);

			if (ioctl(fd, TIOCMSET, &tmp) < 0)
				goto pos_error;
		}
	}

	// 핸드세이크 && RTS
	if ((self->flags & KSERF_NORTS) == 0 &&
		self->prop.handshake != KSERHS_REQTOSEND &&
		self->prop.handshake != KSERHS_REQTOSENDXONXOFF)
	{
		if (ioctl(fd, TIOCMGET, &sigs) < 0)
			goto pos_error;

		if (((sigs & TIOCM_RTS) != 0) != (self->prop.enable_rts != 0))
		{
			tmp = self->prop.enable_rts ? sigs | TIOCM_RTS : sigs&~(TIOCM_RTS);

			if (ioctl(fd, TIOCMSET, &tmp) < 0)
				goto pos_error;
		}
	}

	self->handle = (kintptr)fd;

	self->isconn = TRUE;

	return TRUE;

pos_error:
	close(fd);

	return FALSE;
#endif
#endif
}

/**
 * 시리얼 포트를 닫는다. 닫기만 하고 제거하지는 않으므로 k_sric_open 함수로 다시 열 수 있다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 */
void k_sric_close(kSerialCom* self)
{
#if !NO_UART_SUPP
	k_return_if_fail(self->isconn);

#if _SB_WINDOWS_
	if (self->ov_event)
	{
		HANDLE ev = self->ov_event;
		self->ov_event = NULL;

		SetCommMask((HANDLE)self->handle, 0);

		CloseHandle(ev);
	}

	CloseHandle((HANDLE)self->handle);
	CloseHandle(self->ov_send);
	CloseHandle(self->ov_recv);
#else
	self->useevent = FALSE;

	close((int)self->handle);
#endif

	self->isconn = FALSE;
#endif
}

/**
 * 시리얼 포트의 현재 버퍼에 남아 있는 보내야할 데이터를 모두 삭제한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_discard_send(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(self->isconn, FALSE);

#if _SB_WINDOWS_
	if (!PurgeComm((HANDLE)self->handle, PURGE_TXCLEAR))
		return FALSE;
#else
	if (tcflush((int)self->handle, TCOFLUSH) != 0)
		return FALSE;
#endif

	return TRUE;
#endif
}

/**
 * 시리얼 포트의 현재 버퍼에 남아 있는 읽을 수 있는 데이터를 모두 삭제한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_discard_recv(kSerialCom* self)
{
#if NO_UART_SUPP
	return FALSE;
#else
	k_return_value_if_fail(self->isconn, FALSE);

#if _SB_WINDOWS_
	if (!PurgeComm((HANDLE)self->handle, PURGE_RXCLEAR))
		return FALSE;
#else
	if (tcflush((int)self->handle, TCIFLUSH) != 0)
		return FALSE;
#endif

	return TRUE;
#endif
}

/**
 * 시리얼 포트로 데이터를 전송한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	data		보낼 데이터.
 * @param	offset  	데이터의 옵셋.
 * @param	size		보낼 크기.
 * @return	실제 보낸 데이터의 크기.
 */
int k_sric_send(kSerialCom* self, kconstpointer data, int offset, int size)
{
#if NO_UART_SUPP
	return 0;
#else
#if _SB_WINDOWS_
	HANDLE h;
	const kbyte* ptr;
	DWORD ret, err;
	OVERLAPPED ov;

	k_return_value_if_fail(self->isconn, -1);
	k_return_value_if_fail(data != NULL, 0);
	k_return_value_if_fail(offset >= 0, 0);
	k_return_value_if_fail(size > 0, 0);

	h = (HANDLE)self->handle;
	ptr = (const kbyte*)data;

	memset(&ov, 0, sizeof(OVERLAPPED));
	ov.hEvent = self->ov_send;

	if (!WriteFile(h, ptr + offset, size, &ret, &ov))
	{
		err = GetLastError();

		if (err != ERROR_IO_PENDING && err != ERROR_IO_INCOMPLETE)
			ret = 0;
		else
		{
			if (!GetOverlappedResult(h, &ov, &ret, TRUE))
				ret = 0;
		}
	}

	return (int)ret;
#else
	int fd, n, to;
	kssize_t t;
	const kbyte* ptr;
	struct pollfd pfd;

	k_return_value_if_fail(self->isconn, -1);
	k_return_value_if_fail(data != NULL, 0);
	k_return_value_if_fail(offset >= 0, 0);
	k_return_value_if_fail(size > 0, 0);

	fd = (int)self->handle;
	to = self->prop.to_send;
	ptr = (const kbyte*)data;

	pfd.fd = fd;
	pfd.events = POLLOUT;
	pfd.revents = POLLOUT;

	n = size;

	while (n > 0)
	{
		if (to != 0)
		{
			int ch;

			while ((ch = poll(&pfd, 1, to)) < 0 && errno == EINTR);

			if (ch < 0)
			{
				// 타임 아웃
				return -1;
			}
		}

		do
		{
			t = write(fd, ptr + offset, n);
		}
		while (t < 0 && errno == EINTR);

		if (t < 0)
			return 0;

		offset += t;
		n -= t;
	}

	return size - n;
#endif
#endif
}

/**
 * 시리얼 포트에서 데이터를 받는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	data		받을 데이터가 담길 버퍼.
 * @param	offset  	버퍼의 옵셋.
 * @param	size		읽고 싶은 크기.
 * @return	실제 읽은 크기 언제나 size보다 작다.
 */
int k_sric_recv(kSerialCom* self, kpointer data, int offset, int size)
{
#if NO_UART_SUPP
	return 0;
#else
#if _SB_WINDOWS_
	HANDLE h;
	kbyte* ptr;
	DWORD ret, err;
	OVERLAPPED ov;

	k_return_value_if_fail(self->isconn, -1);
	k_return_value_if_fail(data != NULL, 0);
	k_return_value_if_fail(offset >= 0, 0);
	k_return_value_if_fail(size > 0, 0);

	h = (HANDLE)self->handle;
	ptr = (kbyte*)data;

	memset(&ov, 0, sizeof(OVERLAPPED));
	ov.hEvent = self->ov_recv;

	if (!ReadFile(h, ptr + offset, size, &ret, &ov))
	{
		err = GetLastError();

		if (err != ERROR_IO_PENDING && err != ERROR_IO_INCOMPLETE)
			ret = 0;
		else
		{
			if (!GetOverlappedResult(h, &ov, &ret, TRUE))
				ret = 0;
		}
	}

	return (int)ret;
#else
	int fd, to;
	ksize_t n;
	kbyte* ptr;
	struct pollfd pfd;

	k_return_value_if_fail(self->isconn, -1);
	k_return_value_if_fail(data != NULL, 0);
	k_return_value_if_fail(offset >= 0, 0);
	k_return_value_if_fail(size > 0, 0);

	fd = (int)self->handle;
	to = self->prop.to_recv;
	ptr = (kbyte*)data;

	pfd.fd = fd;
	pfd.events = POLLIN;
	pfd.revents = POLLIN;

	while (poll(&pfd, 1, to) < 0 && errno == EINTR)
	{
		if (errno != EINTR)
		{
			// 오우, 오류
			return 0;
		}
	}

	if ((pfd.revents & POLLIN) == 0)
	{
		// 타임아웃 취급
		return -1;
	}

	n = read(fd, ptr + offset, size);

	return (int)n;
#endif
#endif
}

/**
 * 시리얼 포트에서 1바이트만 읽는다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	받은 바이트, 실패하거나 읽을 데이터가 없으면 -1을 반환한다.
 */
int k_sric_recv_byte(kSerialCom* self)
{
#if NO_UART_SUPP
	return 0;
#else
	kbyte buf[2];

	if (k_sric_recv(self, buf, 0, 1) > 0)
		return buf[0];

	return -1;
#endif
}

/**
 * 시리얼 포트에서 현재 받아 놓은 모든 데이터를 읽어서 반환한다. 반환 데이터는 k_free 함수로 삭제할 수 있다.
 * @param [입력]	self	   	개체나 인터페이스의 자기 자신 값.
 * @param [반환]	outrecv	(널값이 아니면) 받은 데이터의 크기.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 읽은 데이터 버퍼.
 */
kbyte* k_sric_recv_alloc(kSerialCom* self, int* outrecv)
{
#if NO_UART_SUPP
	return NULL;
#else
	kbyte* buf;
	int size, recv;

	k_return_value_if_fail(self->isconn, NULL);

	size = k_sric_get_size_to_recv(self);

	if (size <= 0)
		return NULL;

	buf = k_new(size + 1, kbyte);
	recv = k_sric_recv(self, buf, 0, size);

	if (recv <= 0)
	{
		k_delete(buf);
		return NULL;
	}

	if (outrecv)
		*outrecv = recv;

	return buf;
#endif
}

/**
 * 시리얼 포트에서 백그라운드 이벤트 기능을 사용할 지 여부를 설정한다.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	value   	기능을 쓰려면 참으로 넣는다.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_set_use_event(kSerialCom* self, kcham value)
{
#if NO_UART_SUPP
	return FALSE;
#else
#if _SB_WINDOWS_
	HANDLE h, ev;
	DWORD dw;

	k_return_value_if_fail(self->isconn, FALSE);

	h = (HANDLE)self->handle;

	if (value)
	{
		if (self->ov_event == NULL)
		{
			if (!GetCommMask(h, &dw))
				return FALSE;

			dw |= EV_BREAK | EV_ERR | EV_RXCHAR;

			if (!SetCommMask(h, dw))
				return FALSE;

			self->ispending = FALSE;
#if _SB_WINDOWS_DESKTOP_
			self->ov_event = CreateEvent(NULL, TRUE, FALSE, NULL);
#else
			self->ov_event = CreateEventEx(NULL, NULL, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
#endif
		}
	}
	else
	{
		if (self->ov_event != NULL)
		{
			ev = self->ov_event;
			self->ov_event = NULL;

			if (!GetCommMask(h, &dw))
				dw = 0;
			else
				dw &= ~(EV_BREAK | EV_ERR | EV_RXCHAR);

			SetCommMask(h, dw);

			CloseHandle(ev);
		}
	}
#else
	self->useevent = value;
#endif

	return TRUE;
#endif
}

/**
 * 시리얼 포트에서 백그라운드 이벤트 기능을 처리한다.
 * 반드시 k_sric_set_use_event 함수를 이용하여 활성해 두어야 사용할 수 있다.
 * 보통 스레드를 만들고 그 안에서 이 함수를 폴(poll) 하면 된다.
 * @param [입력]	self  	개체나 인터페이스의 자기 자신 값.
 * @param	waitelapse	타임아웃을 지정할 밀리초. 이 값 만큼 기다렸다 함수를 탈출한다.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_sric_wait_event(kSerialCom* self, int waitelapse)
{
#if NO_UART_SUPP
	return FALSE;
#else
#if _SB_WINDOWS_
	HANDLE h;
	DWORD err;

	k_return_value_if_fail(self->isconn, FALSE);
	k_return_value_if_fail(self->ov_event != NULL, FALSE);

	h = (HANDLE)self->handle;

	if (!self->ispending)
	{
		OVERLAPPED ov;
		DWORD ev = 0;

		memset(&ov, 0, sizeof(OVERLAPPED));
		ov.hEvent = self->ov_event;

		ResetEvent(self->ov_event);

		if (!WaitCommEvent(h, &ev, &ov))
		{
			err = GetLastError();

			if (err == ERROR_IO_PENDING || err == ERROR_IO_INCOMPLETE)
				self->ispending = TRUE;
			else
			{
#if 0
				// 원래 끊어야 함

				// 오류시에는 슬립
				if (waitelapse < 0)
					waitelapse = 100;
				else
					waitelapse = K_CLAMP(waitelapse, 100, 1000);

				k_sleep(waitelapse);
#else
				// 끊음
				k_sric_close(self);
#endif

				return FALSE;
			}
		}
	}

	if (self->ov_event == NULL)
		return FALSE;

#if _SB_WINDOWS_DESKTOP_
	err = WaitForSingleObject(self->ov_event, waitelapse < 0 ? INFINITE : waitelapse);
#else
	err = WaitForSingleObjectEx(self->ov_event, waitelapse < 0 ? INFINITE : waitelapse, FALSE);
#endif

	if (err == WAIT_FAILED)
		return FALSE;
	else if (err == WAIT_TIMEOUT)
		return FALSE;
	else if (err == WAIT_ABANDONED)
	{
		self->ispending = FALSE;
		return FALSE;
	}

	if (self->ispending)
		self->ispending = FALSE;
#else
	int fd;
	struct timeval tv, *ptv;
	fd_set fds;
	int ret;

	k_return_value_if_fail(self->useevent, FALSE);

	fd = (int)self->handle;

	if (waitelapse < 0)
		ptv = NULL;
	else
	{
		tv.tv_sec = waitelapse / 1000;
		tv.tv_usec = (waitelapse % 1000) * 1000;
		ptv = &tv;
	}

	while (self->useevent)
	{
		FD_ZERO(&fds);
		FD_SET(fd, &fds);

		ret = select(fd + 1, &fds, NULL, NULL, ptv);

		if (ret == 0)
		{
			// 타임 아웃
			return FALSE;
		}
		else if (ret < 0)
		{
			// 오류
			return FALSE;
		}
		else
		{
			// 시그널임
			break;
		}
	}

	if (!self->useevent)
		return FALSE;
#endif

	return TRUE;
#endif
}

/**
 * 사용가능한 시리얼 포트를 얻는다.
 * @param [반환]	count	(널값이 아니면) 포트 개수:
 * @return	문제가 있거나 실패하면 널값을 반환, 성공하면 시리얼 포트 목록 문자열. 문자열은 '|'로 구분하여 포트를 나눌 수 있다.
 */
char* k_sric_enum_ports(int* count)
{
#if NO_UART_SUPP
	return NULL;
#else
	char* buf;
	ksize_t len, sum, tmp;
	int cnt;

#if _SB_WINDOWS_
#if _SB_WINDOWS_DESKTOP_
	char name[K_MAX_PATH];
	char desc[K_MAX_PATH];
	kRegistry* rg;
	int i;

	rg = k_regi_new("hklm", "HARDWARE\\DEVICEMAP\\SERIALCOMM", FALSE);

	if (!rg)
		return NULL;

	len = 0;
	sum = K_MAX_PATH;
	buf = k_new(K_MAX_PATH, char);

	for (cnt = i = 0;; i++)
	{
		if (!k_regi_enum(rg, i, name, K_MAX_PATH - 1))
			break;

		if (k_regi_get_string(rg, name, desc, K_MAX_PATH - 1))
		{
			tmp = k_strlen(desc);

			if ((len + tmp) >= sum)
			{
				sum += tmp + K_MAX_PATH;
				buf = k_renew(buf, sum, char);
			}

			k_strcpy(buf + len, desc);
			len += tmp;
			k_strcpy(buf + len, "|");
			len += 1;

			cnt++;
		}
	}

	if (cnt > 0)
		*(buf + len - 1) = '\0';

	k_regi_delete(rg);
#else
	cnt = 0;
	buf = NULL;
#endif
#else
	kDir* dir;
	char desc[K_MAX_PATH];

	dir = k_dir_new("/dev/", 0);

	if (!dir)
		return NULL;

	len = 0;
	sum = K_MAX_PATH;
	buf = k_new(K_MAX_PATH, char);

	for (cnt = 0;;)
	{
		const char* name = k_dir_read(dir);

		if (!name)
			break;

		if (k_strncmp(name, "cua", 3) == 0 ||
			k_strncmp(name, "ttyS", 4) == 0 ||
			k_strncmp(name, "ttyU", 4) == 0 ||
			k_strncmp(name, "ser", 3) == 0 ||
			k_strncmp(name, "s3c", 3) == 0)
		{
			desc[0] = '\0';
			k_strconcat(desc, "/dev/", name, NULL);

			tmp = k_strlen(desc);

			if ((len + tmp) >= sum)
			{
				sum += tmp + K_MAX_PATH;
				buf = k_renew(buf, sum, char);
			}

			k_strcpy(buf + len, desc);
			len += tmp;
			k_strcpy(buf + len, "|");
			len += 1;

			cnt++;
		}
	}

	if (cnt > 0)
		*(buf + len - 1) = '\0';

	k_dir_delete(dir);
#endif

	if (count)
		*count = cnt;

	return buf;
#endif
}
